using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Xml;
using System.Collections.Generic;

namespace FreeFlow.Administration
{
	/// <summary>
	/// A collection of authentication scripts
	/// </summary>
  public sealed class AuthenticationScriptCollection : DatabaseAware, IList, IRefreshable, IEnumerable<AuthenticationScript>
	{
    private List<AuthenticationScript> scripts;
    /// <summary>
    /// Initializes a new instance of the <see cref="AuthenticationScriptCollection"/> class.
    /// </summary>
    /// <param name="server">The server.</param>
		public AuthenticationScriptCollection(Server server) : base(server)
		{
    }

    private void ReadScripts()
    {
      if (scripts == null)
      {
        scripts = new List<AuthenticationScript>();
        byte[] scriptsXml = Server.GetAttachment(AttachmentType.Service, "authproc.xml", "oa");
        if (scriptsXml != null)
        {
          using (MemoryStream stream = new MemoryStream(scriptsXml))
          {
            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.Load(stream);
            if (xmlDoc.DocumentElement != null)
            {
              XmlNodeList childNodes = xmlDoc.DocumentElement.ChildNodes;
              for (int i = 0; i < childNodes.Count; i++)
              {
                if (childNodes[i].Attributes["Default"].Value == "1")
                {
                  // we've got the standard authentication node
                  XmlNode authNode = childNodes[i].ChildNodes[0];
                  for (int j = 0; j < authNode.ChildNodes.Count; j++)
                  {
                    AuthenticationScript script = new AuthenticationScript(Server, this, authNode.ChildNodes[j]);
                    scripts.Add(script);
                  }
                }
                else
                {
                  // guest script
                  XmlNode authNode = childNodes[i].ChildNodes[0].ChildNodes[0];
                  guestUserScript = new AuthenticationScript(Server, this, authNode);
                }
              }
            }
          }
        }
      }
    }

    private AuthenticationScript guestUserScript;
    /// <summary>
    /// Gets the guest user script.
    /// </summary>
    /// <value>The guest user script.</value>
    public AuthenticationScript GuestUserScript
    {
      get
      {
        ReadScripts();
        return guestUserScript;
      }
    }


    #region IList Members

    /// <summary>
    /// Gets a value indicating whether the <see cref="T:System.Collections.IList"/> is read-only.
    /// </summary>
    public bool IsReadOnly
    {
      get
      {
        return false;
      }
    }

    object IList.this[int index]
    {
      get
      {
        ReadScripts();
        if ((index >= 0) && (index < Count))
          return scripts[index];
        return null;
      }
      set
      {
        ReadScripts();
        scripts[index] = (AuthenticationScript)value;
      }
    }

    /// <summary>
    /// Gets or sets the <see cref="AuthenticationScript"/> at the specified index.
    /// </summary>
    public AuthenticationScript this[int index]
    {
      get
      {
        ReadScripts();
        return (AuthenticationScript)scripts[index];
      }
      set
      {
        ReadScripts();
        scripts[index] = value;
        ApplyXml();
      }
    }

    /// <summary>
    /// Removes the <see cref="T:System.Collections.IList"/>
    /// item at the specified index.
    /// </summary>
    /// <param name="index">The zero-based index of the item to remove.</param>
    /// <exception cref="T:System.ArgumentOutOfRangeException">
    /// 	<paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.IList"/>.</exception>
    /// <exception cref="T:System.NotSupportedException">
    /// 	<para>The <see cref="T:System.Collections.IList"/> is read-only.</para>
    /// 	<para>-or-</para>
    /// 	<para>The <see cref="T:System.Collections.IList"/> has a fixed size.</para>
    /// </exception>
    public void RemoveAt(int index)
    {
      ReadScripts();
      scripts.RemoveAt(index);
      ApplyXml();
    }

    void IList.Insert(int index, object value)
    {
      Insert(index, (AuthenticationScript)value);
    }

    /// <summary>
    /// Inserts the authentication script at the specified index.
    /// </summary>
    /// <param name="index">The index.</param>
    /// <param name="value">The authentication script.</param>
    public void Insert(int index, AuthenticationScript value)
    {
      ReadScripts();
      scripts.Insert(index, value);
      ApplyXml();
    }

    void IList.Remove(object value)
    {
      Remove((AuthenticationScript)value);
    }

    /// <summary>
    /// Removes the specified authentication script from the collection.
    /// </summary>
    /// <param name="value">The value.</param>
    public void Remove(AuthenticationScript value)
    {
      ReadScripts();
      scripts.Remove(value);
      if (value == guestUserScript)
        guestUserScript = null;
      ApplyXml();
    }

    bool IList.Contains(object value)
    {
      ReadScripts();
      return scripts.Contains((AuthenticationScript)value);
    }

    /// <summary>
    /// Removes all items from the <see cref="T:System.Collections.IList"/>.
    /// </summary>
    /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IList"/> is read-only.</exception>
    public void Clear()
    {
      ReadScripts();
      scripts.Clear();
    }

    int IList.IndexOf(object value)
    {
      ReadScripts();
      return scripts.IndexOf((AuthenticationScript)value);
    }

    int IList.Add(object value)
    {
      ReadScripts();
      AuthenticationScript script = (AuthenticationScript)value;
      scripts.Add(script);
      return scripts.IndexOf(script);
    }

    /// <summary>
    /// Gets a value indicating whether the <see cref="T:System.Collections.IList"/> has a fixed size.
    /// </summary>
    public bool IsFixedSize
    {
      get
      {
        return false;
      }
    }

    #endregion
    #region ICollection Members

    /// <summary>
    /// Gets a value
    /// indicating whether access to the <see cref="T:System.Collections.ICollection"/> is synchronized
    /// (thread-safe).
    /// </summary>
    public bool IsSynchronized
    {
      get
      {
        return false;
      }
    }

    /// <summary>
    /// Gets the number of scripts contained in the collection.
    /// </summary>
    public int Count
    {
      get
      {
        ReadScripts();
        return scripts.Count;
      }
    }

    /// <summary>
    /// Copies the elements of
    /// the <see cref="T:System.Collections.ICollection"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
    /// </summary>
    /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param>
    /// <param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// 	<paramref name="array"/> is <see langword="null"/>.</exception>
    /// <exception cref="T:System.ArgumentOutOfRangeException">
    /// 	<paramref name="index"/> is less than zero.</exception>
    /// <exception cref="T:System.ArgumentException">
    /// 	<para>
    /// 		<paramref name="array"/> is multidimensional.</para>
    /// 	<para>-or-</para>
    /// 	<para>
    /// 		<paramref name="index"/> is equal to or greater than the length of <paramref name="array"/>.</para>
    /// 	<para>-or-</para>
    /// 	<para>The number of elements in the source <see cref="T:System.Collections.ICollection"/> is greater than the available space from <paramref name="index"/> to the end of the destination <paramref name="array"/>.</para>
    /// </exception>
    /// <exception cref="T:System.InvalidCastException">The type of the source <see cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
    public void CopyTo(AuthenticationScript[] array, int index)
    {
      ReadScripts();
      scripts.CopyTo(array, index);
    }

    void ICollection.CopyTo(Array array, int index)
    {
      CopyTo((AuthenticationScript[])array, index);
    }

    /// <summary>
    /// Gets an object that
    /// can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
    /// </summary>
    public object SyncRoot
    {
      get
      {
        return null;
      }
    }

    #endregion
    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      ReadScripts();
      return scripts.GetEnumerator();
    }

    #endregion
    #region IEnumerable<AuthenticationScript> Members

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
    /// </returns>
    public IEnumerator<AuthenticationScript> GetEnumerator()
    {
      ReadScripts();
      return scripts.GetEnumerator();
    }

    #endregion
    #region IRefreshable Members

    /// <summary>
    /// Refreshes the collection of scripts from the database.
    /// </summary>
    public void Refresh()
    {
      scripts = null;
      guestUserScript = null;
    }

    #endregion

    private static byte[] ReadFile(string fileName)
    {
      using (FileStream stream = new FileStream(fileName, FileMode.Open))
      {
        byte[] data = new byte[stream.Length];
        stream.Read(data, 0, (int)stream.Length);
        return data;
      }
    }

    /// <summary>
    /// Adds the specified script file to the collection.
    /// </summary>
    /// <param name="fileName">Name of the file to add.</param>
    /// <returns>The index of the added file</returns>
    public int Add(string fileName)
    {
      ReadScripts();
      
      byte[] data = ReadFile(fileName);
      AuthenticationScript script = 
        new AuthenticationScript(Server, this, Path.GetFileName(fileName), data);
      script.ApplyChanges();
      scripts.Add(script);
      ApplyXml();
      return scripts.IndexOf(script);
    }

    private void ApplyXml()
    {
      using (MemoryStream stream = new MemoryStream())
      {
        XmlTextWriter writer = new XmlTextWriter(stream, Encoding.ASCII);
        
        writer.WriteStartElement("Authentication");
        writer.WriteStartElement("AuthenticationProcess");
        writer.WriteAttributeString("Name", "Default Process");
        writer.WriteAttributeString("Description", "Standard Metastorm login");
        writer.WriteAttributeString("Default", "1");
        writer.WriteStartElement("SAPList");
        for (int i = 0; i < Count; i++)
        {
          writer.WriteStartElement("SAP");
          writer.WriteAttributeString("Index", i.ToString());
          writer.WriteAttributeString("Name", this[i].Name);
          writer.WriteEndElement(); // SAP
        }
        writer.WriteEndElement(); // SAPList
        writer.WriteEndElement(); // AuthenticationProcess
        
        // guest user stuff
        if (guestUserScript != null)
        {
          writer.WriteStartElement("AuthenticationProcess");
          writer.WriteAttributeString("Name", "Public");
          writer.WriteAttributeString("Description", "");
          writer.WriteAttributeString("Default", "0");
          writer.WriteStartElement("SAPList");
          writer.WriteStartElement("SAP");
          writer.WriteAttributeString("Index", "0");
          writer.WriteAttributeString("Name", guestUserScript.Name);
          writer.WriteEndElement(); // SAP
          writer.WriteEndElement(); // SAPList
          writer.WriteEndElement(); // AuthenticationProcess
        }
        writer.WriteEndElement(); // Authentication

        writer.Flush();
        Server.SetAttachment(AttachmentType.Service, "authproc.xml", "oa", stream.ToArray());
      }
    }

    /// <summary>
    /// Adds a guest script.
    /// </summary>
    /// <param name="fileName">Name of the JScript file to add.</param>
    public void AddGuestScript(string fileName)
    {
      byte[] data = ReadFile(fileName);
      guestUserScript = 
        new AuthenticationScript(Server, this, Path.GetFileName(fileName), data);
      guestUserScript.ApplyChanges();
      ApplyXml();
    }
  }
}
